import asyncio
import math
import time
import requests
from DrissionPage import ChromiumPage

# 定义全局变量
role_cv_map = {}
cv_role_map = {}
is_login = False
chapter_titles = []
chapter_detail_map = {}
current_page_num = 1
total_page_num = 2607


async def start_listening_async(tab, urls):
    """开始异步监听多个URL请求"""
    # 使用通用模式匹配所有需要监听的URL
    # DrissionPage的listen.start()会覆盖之前的监听设置，所以我们需要使用一个通用模式
    
    # 方法1：使用基础域名作为通用模式
    tab.listen.start('https://gateway.kugou.com/')
    
    # 方法2：如果DrissionPage支持正则表达式，可以尝试使用正则模式（取决于库的实现）
    # tab.listen.start(r'https://gateway\.kugou\.com/longebook/.*')
    
    print("已设置监听模式: https://gateway.kugou.com/")
    return len(urls)




import os
import json
from datetime import datetime

# 创建数据保存目录
def ensure_data_dir():
    """确保数据目录存在"""
    data_dir = "captured_data"
    if not os.path.exists(data_dir):
        os.makedirs(data_dir)
    return data_dir

# 初始化数据目录
DATA_DIR = ensure_data_dir()

async def process_packets_async(tab):
    """异步遍历监听到的数据包并处理"""
    while True:
        for packet in tab.listen.steps():
            try:
                # 获取URL和响应内容
                url = packet.request.url
                response_body = packet.response.body
                
                # 生成时间戳用于文件名
                # timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                
                # print(f"捕获到请求: {url}")
                
                # 根据不同的URL进行不同的处理并保存到文件
                if 'longebook/v1/ai_design_album/roles' in url:
                    # 处理角色列表数据
                    # print("处理角色列表数据...")
                    # 使用去重保存函数，避免覆盖之前的数据
                    save_with_deduplication(response_body, f"roles.json")
                    process_roles_data(response_body)
                elif 'longebook/v2/ai_design_anchor_task_center/chapter_list' in url:
                    # 处理章节列表数据

                    # print("处理章节列表数据...")
                    # 使用去重保存函数，避免覆盖之前的数据
                    save_with_deduplication(response_body, f"chapter_list.json")
                    process_chapter_data(response_body)
                elif 'longebook/v1/ai_design_anchor_task_center/chapter_detail' in url:
                    # 处理章节详情数据
                    # print("处理章节详情数据...")
                    
                    # 尝试从URL中提取章节ID
                    chapter_id = extract_chapter_id(url)
                    
                    if chapter_id:
                        # print(f"从URL中提取到chapter_id: {chapter_id}")
                        
                        # 为每个章节ID保存一个单独的文件
                        filename = f"chapter_detail_{chapter_id}.json"
                        
                        # 检查文件是否已存在
                        filepath = os.path.join(DATA_DIR, filename)
                        if os.path.exists(filepath):
                            # print(f"章节详情文件已存在，将更新: {filename}")
                            # 对于章节详情，我们可能希望使用最新的数据
                            # 但也可以选择合并数据
                            save_with_deduplication(response_body, filename)
                        else:
                            # print(f"创建新的章节详情文件: {filename}")
                            save_to_file(response_body, filename)
                    else:
                        # 如果无法提取章节ID，使用时间戳
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                        # print(f"无法从URL中提取chapter_id，使用时间戳")
                        save_to_file(response_body, f"chapter_detail_unknown_{timestamp}.json")
                    
                    # 处理章节详情数据
                    process_chapter_detail_data(response_body)
                else:
                    # 忽略其他不相关的URL
                    pass
            except Exception as e:
                print(f"处理数据包时出错: {str(e)}")
                
        # 使用更合理的休眠时间，减少CPU使用率但保持响应速度
        # await asyncio.sleep(0.5)


def extract_chapter_id(url):
    """从URL中提取章节ID"""
    try:
        # 方法1：使用urllib.parse库解析URL参数（更健壮的方法）
        from urllib.parse import urlparse, parse_qs
        
        # 解析URL
        parsed_url = urlparse(url)
        
        # 解析查询参数
        query_params = parse_qs(parsed_url.query)
        
        # 获取chapter_id参数
        if 'chapter_id' in query_params:
            return query_params['chapter_id'][0]
        
        # 方法2：如果URL中包含chapter_id但不是作为查询参数
        # 例如：/chapter/123 或 /chapter-123
        import re
        
        # 尝试匹配路径中的chapter_id
        path_match = re.search(r'/chapter_id=(\d+)', parsed_url.path)
        if path_match:
            return path_match.group(1)
            
        # 方法3：回退到简单的字符串分割方法
        if 'chapter_id=' in url:
            parts = url.split('chapter_id=')
            if len(parts) > 1:
                chapter_id = parts[1].split('&')[0]
                return chapter_id
                
        # 尝试从URL中提取任何看起来像ID的数字
        id_match = re.search(r'[?&/]id=(\d+)', url)
        if id_match:
            return id_match.group(1)
            
        print(f"无法从URL中提取chapter_id: {url}")
    except Exception as e:
        print(f"提取chapter_id时出错: {str(e)}")
    
    return None


def save_to_file(data, filename, append_mode=False):
    """将数据保存到文件"""
    try:
        filepath = os.path.join(DATA_DIR, filename)
        
        # 如果数据是字典，转换为JSON字符串
        if isinstance(data, dict):
            content = json.dumps(data, ensure_ascii=False, indent=2)
        else:
            # 如果已经是字符串或其他类型，尝试直接保存
            content = str(data)
            
        # 根据模式选择写入方式
        mode = 'a' if append_mode else 'w'
        with open(filepath, mode, encoding='utf-8') as f:
            f.write(content)
        # print(f"数据已{'追加' if append_mode else '保存'}到文件: {filepath}")
    except Exception as e:
        print(f"保存文件时出错: {str(e)}")


def save_with_deduplication(data, filename):
    """读取现有文件，合并新数据，去重后保存"""
    try:
        filepath = os.path.join(DATA_DIR, filename)
        
        # 将输入数据转换为字典（如果是字符串）
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                print("输入数据不是有效的JSON字符串")
                # 如果不是有效的JSON，直接保存
                save_to_file(data, filename)
                return
        
        # 如果文件不存在，直接保存
        if not os.path.exists(filepath):
            save_to_file(data, filename)
            return
            
        # 读取现有文件
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                existing_data = json.load(f)
        except (json.JSONDecodeError, FileNotFoundError):
            # 如果文件不存在或不是有效的JSON，直接保存新数据
            save_to_file(data, filename)
            return
            
        # 合并数据
        if 'data' in data and 'data' in existing_data:
            # 如果两者都有data字段，合并列表
            if 'list' in data['data'] and 'list' in existing_data['data']:
                # 创建一个ID集合，用于去重
                existing_ids = set()
                merged_list = []
                
                # 添加现有数据，并记录ID
                for item in existing_data['data']['list']:
                    item_id = item.get('id', None)
                    if item_id is not None:
                        existing_ids.add(item_id)
                    merged_list.append(item)
                
                # 添加新数据，但跳过已存在的ID
                for item in data['data']['list']:
                    item_id = item.get('id', None)
                    if item_id is None or item_id not in existing_ids:
                        merged_list.append(item)
                        if item_id is not None:
                            existing_ids.add(item_id)
                
                # 更新合并后的列表
                existing_data['data']['list'] = merged_list
                
                # print(f"合并后的数据包含 {len(merged_list)} 个项目")
            else:
                # 如果结构不同，使用新数据
                existing_data = data
        else:
            # 如果结构不同，使用新数据
            existing_data = data
            
        # 保存合并后的数据
        save_to_file(existing_data, filename)
        
    except Exception as e:
        print(f"合并数据时出错: {str(e)}")
        # 出错时，尝试直接保存新数据
        save_to_file(data, filename)


def process_roles_data(data):
    """处理角色列表数据"""
    try:
        if not data:
            print("角色列表数据为空")
            return
            
        # 尝试将字符串转换为JSON（如果它是字符串的话）
        if isinstance(data, str):
            try:
                import json
                data = json.loads(data)
            except json.JSONDecodeError:
                print("角色列表数据不是有效的JSON字符串")
                return
        
        # 检查数据结构
        if not isinstance(data, dict):
            print(f"角色列表数据不是字典格式: {type(data)}")
            return
            
        if 'data' not in data:
            print("角色列表数据缺少'data'字段")
            return
            
        if 'list' not in data['data']:
            print("角色列表数据缺少'list'字段")
            return
            
        # 处理数据 - 只保留少量数据在内存中，其余已保存到文件
        role_count = 0
        cv_count = 0
        
        for item in data['data']['list']:
            if 'name' not in item or 'cv_name' not in item:
                continue
                
            role_name = item['name']
            cv_name = item['cv_name']
            
            # 只保存前100个角色到内存中，避免内存溢出
            if len(role_cv_map) < 100:
                role_cv_map[role_name] = cv_name
                
                if cv_name not in cv_role_map:
                    cv_role_map[cv_name] = [role_name]
                else:
                    cv_role_map[cv_name].append(role_name)
            
            role_count += 1
            if cv_name not in cv_role_map:
                cv_count += 1
                
        print(f"读取主播列表成功，共获取 {role_count} 个角色，{cv_count} 个配音")
        
        # 打印部分数据作为示例
        if role_cv_map:
            sample_items = list(role_cv_map.items())[:3]
            print("示例数据:")
            for role, cv in sample_items:
                print(f"  角色: {role}, 配音: {cv}")
            
            # print("完整数据已保存到文件中")
    except Exception as e:
        print(f"处理角色列表数据时出错: {str(e)}")


def process_chapter_detail_data(data):
    """处理章节详情数据"""
    try:
        if not data:
            print("章节详情数据为空")
            return

        # 尝试将字符串转换为JSON（如果它是字符串的话）
        if isinstance(data, str):
            try:
                import json
                data = json.loads(data)
            except json.JSONDecodeError:
                print("章节详情数据不是有效的JSON字符串")
                return

        # 检查数据结构
        if not isinstance(data, dict):
            print(f"章节详情数据不是字典格式: {type(data)}")
            return

        if 'data' not in data:
            print("章节详情数据缺少'data'字段")
            return
            
        # 打印数据结构以便调试
        # print("章节详情数据结构:")
        # for key in data.keys():
        #     print(f"  - {key}")
            
        # 安全地访问嵌套字段
        if 'data' in data and isinstance(data['data'], dict):
            chapter_data = data['data']
            chapter_id = chapter_data.get('id', 'unknown_id')
            chapter_title = chapter_data.get('title', 'unknown_title')
            
            # print(f"成功处理章节详情: {chapter_title} (ID: {chapter_id})")
            
            # 打印部分章节内容
            content_field = None
            for field in ['content', 'chapter_content', 'text']:
                if field in chapter_data:
                    content_field = field
                    break
                    
            if content_field:
                content = chapter_data[content_field]
                # if isinstance(content, str):
                #     content_preview = content[:100]
                #     # print(f"内容预览: {content_preview}...")
                # else:
                #     print(f"内容类型: {type(content)}")
        else:
            print("章节详情数据结构不符合预期")
            
    except Exception as e:
        print(f"处理章节详情数据时出错: {str(e)}")


def process_chapter_data(data):
    """处理章节列表数据"""
    global chapter_titles
    
    try:
        if not data:
            print("章节列表数据为空")
            return
            
        # 尝试将字符串转换为JSON（如果它是字符串的话）
        if isinstance(data, str):
            try:
                import json
                data = json.loads(data)
            except json.JSONDecodeError:
                print("章节列表数据不是有效的JSON字符串")
                return
        
        # 检查数据结构
        if not isinstance(data, dict):
            print(f"章节列表数据不是字典格式: {type(data)}")
            return
            
        if 'data' not in data:
            print("章节列表数据缺少'data'字段")
            return
            
        if 'list' not in data['data']:
            print("章节列表为空或缺少'list'字段")
            return
            
        # 处理数据 - 只保留章节标题，不存储完整数据
        chapters = data['data']['list']
        
        # 清空之前的章节标题列表，避免重复添加
        chapter_titles = []
        
        # 只存储章节标题
        for i, chapter in enumerate(chapters):
            title = chapter.get('title', f"未命名章节{i+1}")
            chapter_titles.append(title)
        
        # print(f"读取章节列表成功，共获取 {len(chapter_titles)} 个章节")
        # print(f"章节标题: {', '.join(chapter_titles[:5])}" + ("..." if len(chapter_titles) > 5 else ""))
        
        # 打印部分章节信息
        for i, chapter in enumerate(chapters[:3]):
            # print(f"章节 {i+1} 详情:")
            for key, value in chapter.items():
                if isinstance(value, str) and len(value) > 50:
                    value = value[:50] + "..."
                # print(f"  {key}: {value}")
            
        # print("完整章节数据已保存到文件中")
    except Exception as e:
        print(f"处理章节列表数据时出错: {str(e)}")


def login(cur_page):
    global is_login
    start_time = time.time()
    timeout = 300
    while time.time() - start_time < timeout:
        try:
            if "创建专辑" not in cur_page.html:
                print("使用微信扫一扫登录")
                time.sleep(3)
            else:
                is_login = True
                print("恭喜你，登陆成功！")
                break
        except:
            time.sleep(1)
    else:
        print("登录超时，请重试")



def getChapterTitle(cur_page):
    global chapter_titles

    cur_page.get(
        'https://mp.tencentmusic.com/aigc/ndx?aigc=%2Fhuaben%2Fv-514ab38f%2Findex.html%3Fcors%3D1%23%2Fanchor%2Fupload#/anchor/upload?cid=211029&page=1'
    )
    cur_page.wait.load_start()
    cur_page.wait.doc_loaded()
    time.sleep(2)
    try:
        timeout = 100
        start_time = time.time()

        is_clicked = False
        while time.time() - start_time < timeout:
            if is_clicked:
                break
            # 获取所有iframe元素（<iframe>标签）
            iframe_eles = cur_page.eles('t:iframe')  # 通过标签名获取所有iframe元素
            if iframe_eles:
                for iframe_ele in iframe_eles:
                    # 从iframe元素中获取name属性
                    frame_name = iframe_ele.attr("name")
                    if frame_name == "aigc":
                        # 通过名称获取对应的框架对象
                        target_frame = cur_page.get_frame(frame_name)
                        # 使用JavaScript点击
                        click_result = target_frame.run_js("""
                                                                var btn = document.querySelector('div.table-body-col__btn-upload');
                                                                if (btn) {
                                                                    btn.click();
                                                                    return "点击成功";
                                                                } else {
                                                                    return "未找到按钮元素";
                                                                }
                                                            """)
                        

                        print(f"JavaScript点击结果: {click_result}")
                        is_clicked = True
                        break

    except Exception as e:
        print(f"操作失败：{str(e)}")
        print(f"当前URL：{cur_page.url}")
        print(f"iframe数量：{len(cur_page.eles('t:iframe'))}")


import time

def navigateToNextPage(cur_page):
    global current_page_num
    # current_page_num和total_page_num是全局变量，分别表示当前页码和总页码
    with open('full_pge.html', 'w', encoding='utf-8') as f:
        f.write(cur_page.html)
    try:
        start_time = time.time()
        is_end = False
        # 获取所有iframe元素（<iframe>标签）
        iframe_eles = cur_page.eles('t:iframe')  # 通过标签名获取所有iframe元素
        if iframe_eles:
            for iframe_ele in iframe_eles:
                # 从iframe元素中获取name属性
                frame_name = iframe_ele.attr("name")
                if frame_name == "aigc":
                    while True:
                        if is_end:
                            print("已到达最后一页")
                            break
                        # 自动点击章节详情
                        target_frame = cur_page.get_frame(frame_name)
                        # 自动点击章节详情
                        click_result = target_frame.run_js("""
                            // 使用querySelectorAll选择所有带有"word"类的元素
                            var wordElements = document.querySelectorAll('.word');
                            var clicked = false;
                            wordCnt = wordElements.length;
                            if (wordCnt == 0) {
                                return "未找到word元素"
                            }
                            // 遍历所有元素，查找包含"第"字的元素并点击
                            for (var i = 0; i < wordElements.length; i++) {
                                var element = wordElements[i];
                                    // 修复：移除错误的右括号，并使用箭头函数包装点击操作
                                    element.click();
                                    clicked = true;
                            }
                            
                            if (clicked) {
                                return "点击成功" + wordCnt + "个元素";
                            } else {
                                return "未找到包含'第'字的元素" + wordElements.length;
                            }
                                                        """)
                        print(f"章节点击：JavaScript点击结果: {click_result}")
                        # 使用JavaScript点击下一页按钮
                        click_result = target_frame.run_js("""
                           var btn = document.querySelector('button.btn-next');
                           if (btn) {
                               btn.click();
                               return "点击成功";
                           } else {
                               return "未找到按钮元素";
                           }
                       """)

                        current_page_num += 1
                        print(f"翻页: {current_page_num} / {total_page_num}")
                        if 0 < total_page_num <= current_page_num:
                            is_end = True
                            break
                        time.sleep(2)

    except Exception as e:
        print(f"操作失败：{str(e)}")
        print(f"当前URL：{cur_page.url}")
        print(f"iframe数量：{len(cur_page.eles('t:iframe'))}")



def handle_role_for_search():
    with open('roles.json', 'r', encoding='utf-8') as file:
        data = json.load(file)

    # 提取需要的信息
    new_roles = []
    roles = data['data']['list']
    for role in roles:
        new_role = {
            'name': role['name'],
            'intro': role['intro'],
            'cv_name': role['cv_name']
        }
        new_roles.append(new_role)

    # 创建新的 JSON 数据结构
    new_data = {
        'roles': new_roles
    }

    # 将新数据保存为 JSON 文件
    with open('roles_new.json', 'w', encoding='utf-8') as new_file:
        json.dump(new_data, new_file, ensure_ascii=False, indent=4)

async def main():
    global total_page_num
    total_page_num = math.ceil(2607 / 20)
    while True:
        if current_page_num <= total_page_num:
            browser = None
            try:
                print("=== 启动程序 ===")
                print(f"数据将保存到目录: {DATA_DIR}")

                print("初始化浏览器...")
                browser = ChromiumPage()
                tab = browser  # ChromiumPage对象本身就是一个标签页

                # 定义需要监听的URL
                graphql_urls = [
                    'https://gateway.kugou.com/longebook/v1/ai_design_album/roles',
                    'https://gateway.kugou.com/longebook/v2/ai_design_anchor_task_center/chapter_list',
                    'https://gateway.kugou.com/longebook/v1/ai_design_anchor_task_center/chapter_detail',
                    # 可以根据需要添加更多URL
                ]

                print("设置网络监听...")
                await start_listening_async(tab, graphql_urls)

                print(f"已开始监听 {len(graphql_urls)} 个URL:")
                for i, url in enumerate(graphql_urls, 1):
                    print(f"  {i}. {url}")

                # 创建异步任务来处理数据包
                print("创建数据包处理任务...")
                packet_task = asyncio.create_task(process_packets_async(tab))

                # 导航到目标网站
                print("导航到腾讯音乐网站...")
                tab.get('https://mp.tencentmusic.com/index')

                # 继续执行其他代码
                print("等待用户登录...")
                login(tab)

                print("获取章节标题...")
                getChapterTitle(tab)

                # 执行自动翻页
                print("自动翻页中...")
                navigateToNextPage(tab)

                print("程序主流程执行完毕，继续监听网络请求...")
                print("提示: 按Ctrl+C可以终止程序")

                # 等待数据包处理任务完成（实际上这个任务会一直运行，直到程序被终止）
                try:
                    await packet_task
                except asyncio.CancelledError:
                    print("数据包处理任务被取消")

            except KeyboardInterrupt:
                print("\n用户终止程序")
            except Exception as e:
                print(f"程序执行出错: {str(e)}")
                import traceback
                traceback.print_exc()
            finally:
                # 关闭浏览器
                if browser:
                    try:
                        browser.quit()
                        print("浏览器已关闭")
                    except:
                        pass
                print("=== 程序结束 ===")
        else:
            break

def handle_exit():
    """处理程序退出"""
    print("正在退出程序...")
    # 可以在这里添加清理代码
    print("退出完成")


if __name__ == "__main__":

    try:
        # 注册退出处理函数
        import atexit
        atexit.register(handle_exit)

        # 运行主程序
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n程序被用户中断")
    except Exception as e:
        print(f"程序运行出错: {str(e)}")
        import traceback
        traceback.print_exc()